/**
 * @file mat.cpp
 * @author 逆流 (1171267147@qq.com)
 * @brief Mat-基础图像容器
 * @version 0.1
 * @date 2024-06-29
 *
 * @copyright Copyright (c) 2024
 * https://docs.opencv.org/4.x/d6/d6d/tutorial_mat_the_basic_image_container.html
 *
 */

#include <opencv2/flann/result_set.h>

#include <opencv2/opencv.hpp>
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"
#include "env.h"

using namespace cv;
using namespace std;

TEST_CASE("Mat-Basic Image Container") {
    Mat A, C;

    A = imread(kResourcesDir + "test01.jpg", IMREAD_COLOR);

    /// Mat 可以自动管理内存，不需要手动分配和释放
    /// Mat 内部由两部分组成，header 和 pointer
    /// - header 包含矩阵大小、存储方法、矩阵存储地址等信息，大小是恒定的
    /// - pointer 指向包含像素值的矩阵的指针
    /// //!这里是指为像素值矩阵分配的存储空间 opencv 采用参考计数系统，不同 Mat
    /// 对象可以通过 pointer来共享同一个图像矩阵数据，
    /// 在执行复制运算时，只会复制标头，而不复制数据本身

    SUBCASE("only copy header") {
        Mat B(A);
        C = A;
        CHECK(A.data == B.data);
        CHECK(A.data == C.data);
    }

    /// 通过 ROI 区域来引用其它矩阵的子矩阵数据

    SUBCASE("roi") {
        Mat D(A, Rect(10, 10, 100, 100));
        Mat E = A(Range::all(), Range(1, 3));

        CHECK_EQ(D.rows, 100);
        CHECK_EQ(D.cols, 100);
        CHECK_EQ(E.rows, A.rows);
        CHECK_EQ(E.cols, 2);
    }

    /// 当有人复制 Mat 对象的标题时，矩阵的计数器就会增加；
    /// 每当清理标头时，此计数器都会减少。当计数器达到零时，矩阵被释放
    /// 如果想复制矩阵数据本身，可以通过 cv::Mat::clone() 或 cv::Mat::copyTo()
    /// 来实现

    SUBCASE("copy data") {
        Mat F = A.clone();
        Mat G;
        A.copyTo(G);
        CHECK(A.data != F.data);
        CHECK(A.data != G.data);
    }
}

/// OpenCV 使用 RGB 系统来描述每一个像素点的颜色，也可以增加透明度(A)
/// 每一个颜色分量都存在有效值域，根据数据类型不同，范围也不同。
/// 对于不同类型，定义格式如下:
/// ```
/// CV_[数据的类型的字节数]][有无符号][类型前缀]C[通道号]
/// // CV_8UC3 表示 8 位无符号的 3 通道图像
/// ```

TEST_CASE("Create Mat") {
    SUBCASE("Mat constructor") {
        Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255));
        cout << "M = " << endl << " " << M << endl << endl;
    }

    SUBCASE("use c/c++ array init") {
        int sz[3] = {2, 2, 2};
        Mat L(3, sz, CV_8UC(1), Scalar::all(0));
        // cout << "L = " << endl << " " << L << endl << endl;
        // !超过2维，无法使用常规方法输出
    }

    SUBCASE("create function") {
        Mat M;
        M.create(4, 4, CV_8UC(2));
        cout << "M = " << endl << " " << M << endl << endl;
    }

    SUBCASE("Matlab style") {
        Mat E = Mat::eye(4, 4, CV_64F);
        cout << "E = " << endl << " " << E << endl << endl;
        Mat O = Mat::ones(4, 4, CV_32F);
        cout << "O = " << endl << " " << O << endl << endl;
        Mat Z = Mat::zeros(3, 3, CV_8UC1);
        cout << "Z = " << endl << " " << Z << endl << endl;
    }

    SUBCASE("comma separated initializers") {
        Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
        cout << "C = " << endl << " " << C << endl << endl;
#if _cplusplus >= 201103L
        Mat D = (Mat_<double>({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3);
        cout << "D = " << endl << " " << D << endl << endl;
#endif
    }

    SUBCASE("randu") {
        Mat R = Mat(3, 2, CV_8UC3);
        randu(R, Scalar::all(0), Scalar::all(255));
        cout << "R = " << endl << " " << R << endl << endl;
    }
}

TEST_CASE("Output of Mat") {
    Mat R = Mat(3, 2, CV_8UC3);
    randu(R, Scalar::all(0), Scalar::all(255));

    cout << "R(default) = " << endl << " " << R << endl << endl;
    cout << "R(matlab) = " << endl << " " << format(R, Formatter::FMT_MATLAB) << endl << endl;
    cout << "R(csv) = " << endl << " " << format(R, Formatter::FMT_CSV) << endl << endl;
    cout << "R(python) = " << endl << " " << format(R, Formatter::FMT_PYTHON) << endl << endl;
    cout << "R(numpy) = " << endl << " " << format(R, Formatter::FMT_NUMPY) << endl << endl;
    cout << "R(c) = " << endl << " " << format(R, Formatter::FMT_C) << endl << endl;
}

TEST_CASE("Output of other common items") {
    Point2f p(5, 1);
    cout << "Point(2D) = " << p << endl;

    Point3f q(5, 1, 2);
    cout << "Point(3D) = " << q << endl;

    vector<float> v;
    v.push_back(static_cast<float>(CV_PI));
    v.push_back(2);
    v.push_back(3.01f);
    cout << "Vector of floats via Mat = " << Mat(v) << endl;

    vector<Point2f> vp(20);
    for (std::size_t i = 0; i < vp.size(); i++) {
        vp[i] = Point2f(static_cast<float>(i * 5), static_cast<float>(i % 7));
    }
    cout << "Vector of Points = " << vp << endl;
}
